#include <stdio.h>
#include <stdlib.h>

/*


*/

void define_pointer(void);
void sizeof_pointer(void);
void requesting_memory(void);
void using_pointer(void);
void NULL_pointer(void);
void access_array_by_pointer(void);

int a,b,c,d;
const int MAX = 3;


void main(void)
{


    printf("***********define pointer**************\n");
    define_pointer();

    sizeof_pointer();

    requesting_memory();

    printf("***********using pointer**************\n");
    using_pointer();
    printf("%p,%p, %p,%p\n", &a, &b,&c,&d);
    NULL_pointer();
    access_array_by_pointer();
     system("pause");
}


void define_pointer(void)
{
    // int *ptr;             //Create a pointer that points to random memory address

    // *ptr = 20;            //Dereference that pointer, 
    //                       // and assign a value to random memory address.
    //                       //Depending on external (not inside your program) state
    //                       // this will either crash or SILENTLY CORRUPT another 
    //                       // data structure in your program.  

    // printf("*ptr: %d\n", *ptr);   //Print contents of same random memory address
    //                       // May or may not crash, depending on who owns this address


    int *ptr1;              //Create pointer to random memory address

    int q = 50;            //Create local variable with contents int 50

    ptr1 = &q;              //Update address targeted by above created pointer to point
                           // to local variable your program properly created

    printf("*ptr1: %d\n", *ptr1);    //Happily print the contents of said local variable (q)


    void *memory = malloc(sizeof(int));
    int *ptr2 = (int *)memory;
    *ptr2 = 20;
    printf("*ptr2: %d\n", *ptr2);
    free(memory);

    int var_i = 10;
    int *p ; // define pointer variable
    
    /* 
    A pointer is **a type of variable** which can store the address of another object or a function.
    inside a function, pointer is uninitialized and doesn't point to any valid object yet */
    int *iptr1, *iptr2;
    /* iptr3 is a pointer variable, whereas iptr4 is misnamed and is an int */
    int *iptr3, iptr4;
    /*
    The address-of or reference operator denoted by an ampersand (&) **gives the address of a given variable** 
    which can be placed in a pointer of appropriate type.
    */
    p = &var_i;

    printf("var_i storage address: %p\n", p);
    /*
    The indirection or dereference operator denoted by an asterisk (*) **gets the contents of an object** pointed to by a pointer.
    */
    printf("*var_i value: %d\n", *p );

    /*
    If the pointer points to a structure or union type then you can dereference it 
    and access its members directly using the -> operator:
    */
   struct SomeStruct
   {
       int i;
       int someMember;
   } someObject;

    // struct SomeStruct *s = &someObject;

    // struct SomeStruct *s = malloc(sizeof *s);
    // s->someMember = 0; /* UNSAFE, because s might be a null pointer */

    struct SomeStruct *s = malloc(sizeof *s);
    if (s)
    {
        s->someMember = 0; /* This is safe, we have checked that s is valid */
    }

    s->someMember = 5; /* Equivalent to (*s).someMember = 5 */
    printf("s->someMember: %d\n", s->someMember);
    printf("(*s).someMember: %d\n", (*s).someMember);

    int *pointer;
    printf("Value of the pointer itself: %p\n", (void *)pointer);
    /* Value of the pointer itself: 0x7ffcd41b06e4 */
    /* This address will be different each time the program is executed */
    // Because a pointer is a mutable variable, it is possible for it to not point to a valid object, either by being set to null
    pointer = 0; /* or alternatively */
    printf("Value of the pointer itself -> 0: %p\n", (void *)pointer);
    pointer = NULL;
    printf("Value of the pointer itself -> NULL: %p\n", (void *)pointer);
    // if (!pointer) exit(EXIT_FAILURE);

}

void sizeof_pointer(void)
{
    printf("***** sizeof_pointer *****\n");
    /*
    Although pointer must be of a specific type, the memory allocated for each type of pointer is equal to the memory
used by the environment to store addresses, rather than the size of the type that is pointed to.
    
    Note that the results above can vary from environment to environment in numbers but all environments would
show equal sizes for different types of pointer
    */
    printf("Size of int pointer: %u\n", sizeof (int*)); /* size 4 bytes */
    printf("Size of int variable: %u\n", sizeof (int)); /* size 4 bytes */
    printf("Size of char pointer: %u\n", sizeof (char*)); /* size 4 bytes */
    printf("Size of char variable: %u\n", sizeof (char)); /* size 1 bytes */
    printf("Size of short pointer: %u\n", sizeof (short*)); /* size 4 bytes */
    printf("Size of short variable: %u\n", sizeof (short)); /* size 2 bytes */
}

void requesting_memory(void)
{
    printf("***** requesting_memory *****\n");
    /*
    For a given compiler/machine configuration, types have a known size; 
    however, there isn't any standard which defines that the size of a given type (other than char) will be the same for all compiler/machine configurations.
    
    sizeof(that_type) or sizeof(*var_ptr_to_that_type)
    */
   // Non-portable allocation:
    // int *intPtr = malloc(4*1000); /* allocating storage for 1000 int */
    // printf("malloc(4*1000) size: %d\n", sizeof(*intPtr)); // 4
    // long *longPtr = malloc(8*1000); /* allocating storage for 1000 long */
    // printf("malloc(8*1000) size: %d\n", sizeof(*longPtr)); // 4

    // // Portable allocation:
    // int *intPtr = malloc(sizeof(int)*1000); /* allocating storage for 1000 int */
    // printf("malloc(sizeof(int)*1000) size: %d\n", sizeof(*intPtr));
    // long *longPtr = malloc(sizeof(long)*1000); /* allocating storage for 1000 long */
    // printf("malloc(sizeof(long)*1000) size: %d\n", sizeof(*longPtr));

    // Or, better still:
    int *intPtr = malloc(sizeof(*intPtr)*1000); /* allocating storage for 1000 int */
    printf("malloc(sizeof(*intPtr)*1000) size: %d\n", sizeof(*intPtr)); // 4
    long *longPtr = malloc(sizeof(*longPtr)*1000); /* allocating storage for 1000 long */
    printf("malloc(sizeof(*longPtr)*1000) size: %d\n", sizeof(*longPtr)); // 4
}

void using_pointer(void)
{
   int  var = 20;   /* 实际变量的声明 */
   int  *ip;        /* 指针变量的声明 */
 
   ip = &var;  /* 在指针变量中存储 var 的地址 */
 
   printf("var storage address: %p\n", &var  );
 
   /* 在指针变量中存储的地址 */
   printf("ip storage address: %p\n", ip );
 
   /* 使用指针访问值 */
   printf("*ip value: %d\n", *ip );
}


void NULL_pointer(void)
{
    int *ptr = NULL;

    printf("ptr address: %p\n", ptr);
}

void access_array_by_pointer(void)
{
    // 我们喜欢在程序中使用指针代替数组，因为变量指针可以递增，而数组不能递增，数组可以看成一个指针常量
    /*
    Arrays in C are always held in contiguous locations in memory.
    Pointer arithmetic is always scaled by the size of the item pointed to


    */
   int  var[] = {10, 100, 200};
   int  i, *ptr;
 
   /* 指针中的数组地址 */
   ptr = var;
   for ( i = 0; i < MAX; i++)
   {
 
      printf("storage address: var[%d] = %p\n", i, ptr );
      printf("storage value: var[%d] = %d\n", i, *ptr );
 
      /* 指向下一个位置 */
      ptr++;
   }
}


/*


每一个变量都有一个内存位置，
每一个内存位置都定义了可使用 & 运算符访问的地址，它表示了在内存中的一个地址。

指针也就是内存地址，指针变量是用来存放内存地址的变量
    地址都是一个代表内存地址长的十六进制数
    不同数据类型的指针之间唯一的不同是，指针所指向的变量或常量的数据类型不同
    type *var-name;
        type 是指针的基类型，它必须是一个有效的 C 数据类型，
        var-name 是指针变量的名称。
        用来声明指针的星号 * 与乘法中使用的星号是相同的，星号是用来指定一个变量是指针

p是一个指针， 存储着变量var_i的地址
指针p的类型与变量var_i的类型一致，因为整行的指针只能存储整型变量的指针地址




*/